Introduction to mrgsolve

ACoP10 2019

Metrum Research Group

Pharmacokinetics

Dosing regimens

Dosing regimens

Population simulation

PKPD

. # A tibble: 1 x 1
.   Median_CFB
.        <dbl>
. 1      -14.4

Sensitivity analysis

Mechanistic models

About mrgsolve

mrgsolve started as QSP modeling tool

Orientation

What we will cover today

  1. Three basic workflows
  2. Loading the model into R
  3. Event objects
  4. Data sets

Emphasis is on getting you running your own simulations today.

A basic simulation with mrgsolve

mod %>% ev(amt = 100, ii = 24, addl = 3) %>% mrgsim() %>% plot()

The first / simplest workflow.

A basic simulation with mrgsolve

mod %>% ev(amt = 100, ii = 24, addl = 3) %>% mrgsim() %>% plot()

Why do we use %>% ?

What happens first in this operation?

mean(sqrt(seq(4)))

Pipelines

mean(sqrt(seq(4)))
4  %>% seq() %>% sqrt() %>% mean()

Better.

4  %>% 
  seq(.) %>% 
  sqrt(.) %>% 
  mean(., na.rm = TRUE)
mod %>% some_intervention() %>% simulate() %>% post_process()

The model object

model %>% ...

Take a look: overview

mod


-----------------  source: pk1.cpp  -----------------

  project: /Users/kyleb/Rli...gsolve/models
  shared object: pk1-so-4d02475ef035 

  time:          start: 0 end: 192 delta: 0.2
                 add: <none>

  compartments:  EV CENT [2]
  parameters:    CL V KA [3]
  captures:      CP [1]
  omega:         0x0 
  sigma:         0x0 

  solver:        atol: 1e-08 rtol: 1e-08 maxsteps: 20k
------------------------------------------------------

Take a look: parameters (really important)

param(mod)

 Model parameters (N=3):
 name value . name value
 CL   1     | V    20   
 KA   1     | .    .    

Any advanced simulation will need to manipulate the model parameters in some way or another

Take a look: compartments

init(mod)

 Model initial conditions (N=2):
 name       value . name     value
 CENT (2)   0     | EV (1)   0    

Where did mod come from?

mod <- mread("simple", "../../model")
mod <- mread("<model-name>", "<project-directory>")

Internal model library

Quiz:

mod <- mread("<first-argument>", "<second-argument>")

Internal model library

Quiz:

mod <- mread("<first-argument>", "<second-argument>")
mod <- mread("<first-argument>", modlib())
modlib()
. [1] "/Users/kyleb/Rlibs/mrgsolve/models"

Internal model library

mod <- mread("effect", modlib())
mod
. 
. 
. ----------------  source: effect.cpp  ----------------
. 
.   project: /Users/kyleb/Rli...gsolve/models
.   shared object: effect-so-4f5677552ab8 
. 
.   time:          start: 0 end: 36 delta: 0.1
.                  add: <none>
. 
.   compartments:  GUT CENT PERIPH CE [4]
.   parameters:    VC KA K10 K12 K21 E0 EMAX EC50 KEO [9]
.   captures:      EFFECT CP [2]
.   omega:         0x0 
.   sigma:         0x0 
. 
.   solver:        atol: 1e-08 rtol: 1e-08 maxsteps: 20k
. ------------------------------------------------------

Try this now

mod <- mread("pbpk", modlib())
mod <- modlib("pbpk")

Oops

mod <- try(mread("simple"))
. Error : the model file simple.cpp does not exist.
mod
. [1] "Error : the model file simple.cpp does not exist.\n"
. attr(,"class")
. [1] "try-error"
. attr(,"condition")
. <simpleError: the model file simple.cpp does not exist.>

Your turn

Summary:


Roadmap

We use mread() to get a model loaded into R

Next, we’ll need

Event objects

e <- ev(amt = 100) 

e
. Events:
.   time amt cmt evid
. 1    0 100   1    1

What to include in ev(...)

Interventions and corresponding evid


demo

Three ways to invoke

Inline

mod %>% ev(amt = 100) %>% mrgsim()

Object via pipeline

e <- ev(amt = 100)

mod %>% ev(e) %>% mrgsim()

As argument

mod %>% mrgsim(events = e)

Event objects are just data frames

as.data.frame(e)
.   time amt cmt evid
. 1    0 100   1    1

Simulate


model %>% intervention %>% simulate


e <- ev(amt = 100)

mod %>% ev(e) %>% mrgsim()
. Model:  effect 
. Dim:    362 x 8 
. Time:   0 to 36 
. ID:     1 
.     ID time    GUT   CENT PERIPH     CE EFFECT     CP
. 1:   1  0.0   0.00  0.000 0.0000 0.0000  157.0  0.000
. 2:   1  0.0 100.00  0.000 0.0000 0.0000  157.0  0.000
. 3:   1  0.1  91.21  8.443 0.1552 0.2225  155.7  3.460
. 4:   1  0.2  83.19 15.502 0.5817 0.8048  152.8  6.353
. 5:   1  0.3  75.88 21.354 1.2272 1.6382  149.6  8.752
. 6:   1  0.4  69.21 26.156 2.0463 2.6356  146.6 10.719
. 7:   1  0.5  63.13 30.046 3.0001 3.7278  144.1 12.314
. 8:   1  0.6  57.58 33.146 4.0553 4.8607  142.2 13.584
mrgsim(mod, events = e)

Simulate and plot

We need to know this to work the example; more details in a bit

mod %>% ev(e) %>% mrgsim() %>% plot()

Your turn

Customizing output

What would you like to “fix” in this plot?

mod %>% ev(amt = 100) %>% mrgsim() %>% plot(CP~time)

Simulation time grid

stime(mod)
. [1]  0  6 12

Simulation end time

mod %>% ev(amt = 100) %>% mrgsim(end = 48) %>% plot(CP~time)

Simulation time step

mod %>% ev(amt = 100) %>% mrgsim(end = 48, delta = 0.1) %>% plot(CP~time)

Working with simulated output


model %>% intervention %>% simulate %>% take-a-look


Plot

mod <- mread_cache("effect", modlib())

mod %>% ev(amt = 100) %>% mrgsim() %>% plot()

Plot

mod %>% ev(amt = 100) %>% mrgsim() %>% 
  plot(CP + EFFECT~., col = "firebrick")

Pipeline to dplyr functions

mod %>% ev(amt = 100) %>% mrgsim() %>% mutate(arm = 1)
. # A tibble: 362 x 9
.       ID  time   GUT  CENT PERIPH    CE EFFECT    CP   arm
.    <dbl> <dbl> <dbl> <dbl>  <dbl> <dbl>  <dbl> <dbl> <dbl>
.  1     1   0     0    0     0     0       157   0        1
.  2     1   0   100    0     0     0       157   0        1
.  3     1   0.1  91.2  8.44  0.155 0.222   156.  3.46     1
.  4     1   0.2  83.2 15.5   0.582 0.805   153.  6.35     1
.  5     1   0.3  75.9 21.4   1.23  1.64    150.  8.75     1
.  6     1   0.4  69.2 26.2   2.05  2.64    147. 10.7      1
.  7     1   0.5  63.1 30.0   3.00  3.73    144. 12.3      1
.  8     1   0.6  57.6 33.1   4.06  4.86    142. 13.6      1
.  9     1   0.7  52.5 35.6   5.18  5.99    141. 14.6      1
. 10     1   0.8  47.9 37.4   6.36  7.09    139. 15.3      1
. # … with 352 more rows

If we didn’t modify the output

mod %>% mrgsim()
. Model:  effect 
. Dim:    361 x 8 
. Time:   0 to 36 
. ID:     1 
.     ID time GUT CENT PERIPH CE EFFECT CP
. 1:   1  0.0   0    0      0  0    157  0
. 2:   1  0.1   0    0      0  0    157  0
. 3:   1  0.2   0    0      0  0    157  0
. 4:   1  0.3   0    0      0  0    157  0
. 5:   1  0.4   0    0      0  0    157  0
. 6:   1  0.5   0    0      0  0    157  0
. 7:   1  0.6   0    0      0  0    157  0
. 8:   1  0.7   0    0      0  0    157  0

Other connections to dplyr

Your turn

Updating the model object

Update

On the fly

mod %>% update(end = 72) %>% mrgsim()

Persistent

mod <- update(mod, end = 72)

mrgsim will call update for you (on the fly)

mod %>% mrgsim(end = 72)

What else can I update?

mod %>% update(rtol = 1E-12) %>% ...
mod %>% mrgsim(rtol = 1E-12) %>% ...

Updating parameters

mod <- param(mod, CL = 1.5)

Add a population element to the simulation


This is our second simulation workflow.

One population simulation with mrgsolve

mod %>%
ev(amt = 100, ii = 24, addl = 3) %>%
idata_set(pop) %>%
mrgsim() %>% plot()

idata_set takes in individual-level data

head(pop, n = 3)
.   ID   CL   VC     KA KOUT IC50 FOO
. 1  1 1.05 47.8 0.8390 2.45 1.28   4
. 2  2 0.73 30.1 0.0684 2.51 1.84   6
. 3  3 2.82 23.8 0.1180 3.88 2.48   5
length(unique(pop$ID))
. [1] 10

pop and mod are connected via parameters

head(pop, n = 3)
.   ID   CL   VC     KA KOUT IC50 FOO
. 1  1 1.05 47.8 0.8390 2.45 1.28   4
. 2  2 0.73 30.1 0.0684 2.51 1.84   6
. 3  3 2.82 23.8 0.1180 3.88 2.48   5
param(mod)
. 
.  Model parameters (N=3):
.  name value . name value
.  CL   1     | V    20   
.  KA   1     | .    .

What else can we do with idata?

Batches of simulations or sensitivity analyses

idata <- expand.idata(CL = seq(0.5, 1.5, 0.25))
idata
.   ID   CL
. 1  1 0.50
. 2  2 0.75
. 3  3 1.00
. 4  4 1.25
. 5  5 1.50

Note: this is the event + idata_set configuration

mod %>%
  idata_set(idata) %>%
  ev(amt = 100, ii = 24, addl = 2) %>%
  mrgsim(end = 120) %>% 
  plot(CP ~ ., logy=TRUE)

Your turn

data_set is the dosing equivalent to idata_set

data <- expand.ev(amt = c(100, 300, 1000), ii = 24, addl = 3)

head(data)
.   ID time  amt ii addl cmt evid
. 1  1    0  100 24    3   1    1
. 2  2    0  300 24    3   1    1
. 3  3    0 1000 24    3   1    1

Note: this is data_set configuration

mod %>%
  data_set(data) %>%
  mrgsim(end = 120) %>% plot(log(CP) ~ .)

data_set can also carry parameters

data <- expand.ev(
  amt = c(100,300,1000),
  ii = 24, addl = 3,
  CL = seq(0.5,1.5, 0.5)
)

head(data)
.   ID time  amt ii addl cmt evid  CL
. 1  1    0  100 24    3   1    1 0.5
. 2  2    0  300 24    3   1    1 0.5
. 3  3    0 1000 24    3   1    1 0.5
. 4  4    0  100 24    3   1    1 1.0
. 5  5    0  300 24    3   1    1 1.0
. 6  6    0 1000 24    3   1    1 1.0
data <- mutate(data, dose = amt)

mod %>%
  data_set(data) %>%
  mrgsim(carry_out = "dose", end = 120) %>%
  plot(log(CP)~time|factor(dose), group = ID, scales = "same")

carry_out

Copy from the input data to the output data

mod %>% carry_out(dose) %>% data_set(data, dose==300) %>% mrgsim()
. Model:  pk1 
. Dim:    2886 x 4 
. Time:   0 to 192 
. ID:     3 
.     ID time dose     CP
. 1:   2  0.0  300  0.000
. 2:   2  0.0  300  0.000
. 3:   2  0.2  300  2.712
. 4:   2  0.4  300  4.919
. 5:   2  0.6  300  6.712
. 6:   2  0.8  300  8.167
. 7:   2  1.0  300  9.345
. 8:   2  1.2  300 10.296

data_set and ev

head(data, n = 3)
.   ID time  amt ii addl cmt evid  CL dose
. 1  1    0  100 24    3   1    1 0.5  100
. 2  2    0  300 24    3   1    1 0.5  300
. 3  3    0 1000 24    3   1    1 0.5 1000
ev(amt = 100, ii = 24, addl = 3)
. Events:
.   time amt ii addl cmt evid
. 1    0 100 24    3   1    1

We have now seen 3 simulation setups

Wait a minute …

head(data)
.   ID time  amt ii addl cmt evid  CL dose
. 1  1    0  100 24    3   1    1 0.5  100
. 2  2    0  300 24    3   1    1 0.5  300
. 3  3    0 1000 24    3   1    1 0.5 1000
. 4  4    0  100 24    3   1    1 1.0  100
. 5  5    0  300 24    3   1    1 1.0  300
. 6  6    0 1000 24    3   1    1 1.0 1000

Generating data sets

Recall that data sets are just plain old data.frames in R. Feel free to code these up in any way that is convenient for you.

In our experience, the following helper functions cover many (not every) common needs for building the data sets.

expand.ev

expand.ev(ID = 1:2, amt = c(100,200))
.   ID time amt cmt evid
. 1  1    0 100   1    1
. 2  2    0 100   1    1
. 3  3    0 200   1    1
. 4  4    0 200   1    1

as_data_set

data <- as_data_set(
  ev(amt = 100, ii = 12, addl = 19, ID = 1:2),
  ev(amt = 200, ii = 24, addl = 9,  ID = 1:3),
  ev(amt = 150, ii = 24, addl = 9,  ID = 1:4)
)
.   ID time cmt evid amt ii addl
. 1  1    0   1    1 100 12   19
. 2  2    0   1    1 100 12   19
. 3  3    0   1    1 200 24    9
. 4  4    0   1    1 200 24    9
. 5  5    0   1    1 200 24    9
. 6  6    0   1    1 150 24    9
. 7  7    0   1    1 150 24    9
. 8  8    0   1    1 150 24    9
. 9  9    0   1    1 150 24    9

ev_rep

e <- ev(amt = 100, ii = 12, addl = 14)
ev_rep(e, ID = seq(5))
.   time amt ii addl cmt evid ID
. 1    0 100 12   14   1    1  1
. 2    0 100 12   14   1    1  2
. 3    0 100 12   14   1    1  3
. 4    0 100 12   14   1    1  4
. 5    0 100 12   14   1    1  5
e <- ev(amt = 100, ii = 12, addl = 14, ID = seq(5))

ev_rep

e <- seq(ev(amt = 100), wait = 36, ev(amt = 50, ii = 24, addl = 4))
e
. Events:
.   time amt ii addl cmt evid
. 1    0 100  0    0   1    1
. 2   36  50 24    4   1    1
ev_rep(e, ID = seq(2))
.   time amt ii addl cmt evid ID
. 1    0 100  0    0   1    1  1
. 2   36  50 24    4   1    1  1
. 3    0 100  0    0   1    1  2
. 4   36  50 24    4   1    1  2

ev_assign

pop <- expand.idata(GROUP = c(1,2), ID = 1:3)
head(pop)
.   ID GROUP
. 1  1     1
. 2  2     2
. 3  3     1
. 4  4     2
. 5  5     1
. 6  6     2
e1 <- ev(amt = 100, ii = 24, addl = 9)
e2 <- mutate(e1, amt = 200)

data <- ev_assign(
  l = list(e1,e2), 
  idata = pop, 
  evgroup = "GROUP",
  join = TRUE
)

head(data)
.   time amt ii addl cmt evid ID GROUP
. 1    0 100 24    9   1    1  1     1
. 2    0 200 24    9   1    1  2     2
. 3    0 100 24    9   1    1  3     1
. 4    0 200 24    9   1    1  4     2
. 5    0 100 24    9   1    1  5     1
. 6    0 200 24    9   1    1  6     2

Your turn

Create complex events - 1

What’s going to happen?

e1 <- ev(amt = 200) 

e2 <- ev(amt = 100, time = 24, ii = 24, addl = 2)

c(e1, e2)

Create complex events - 1

What’s going to happen?

Create complex events - 1

Combine two events

e1 <- ev(amt = 200) 

e2 <- ev(amt = 100, time = 24, ii = 24, addl = 2)

c(e1, e2)
. Events:
.   time amt cmt evid ii addl
. 1    0 200   1    1  0    0
. 2   24 100   1    1 24    2

Create complex events - 2

What’s going to happen?

e1 <- ev(amt = 200, ii = 12, addl = 2) 

e2 <- ev(amt = 100, ii = 24, addl = 4)

seq(e1, e2)

Create complex events - 2

What’s going to happen?

Create complex events - 2

Put two events in a sequence

e1 <- ev(amt = 200, ii = 12, addl = 2) 
e2 <- ev(amt = 100, ii = 24, addl = 4)

seq(e1, e2)
. Events:
.   time amt ii addl cmt evid
. 1    0 200 12    2   1    1
. 2   36 100 24    4   1    1

Create complex events - 3

What is going to happen?

e1 <- ev(amt = 200) 

e2 <- ev(amt = 100, ii = 24, addl = 2)

seq(e1, wait = 36, e2)

Create complex events - 3

What is going to happen?

Create complex events - 3

Wait before starting the next part of the regimen

e1 <- ev(amt = 200) 

e2 <- ev(amt = 100, ii = 24, addl = 2)

seq(e1, wait = 36, e2)
. Events:
.   time amt ii addl cmt evid
. 1    0 200  0    0   1    1
. 2   36 100 24    2   1    1

Your turn

We’re stil working on this setup


model %>% intervention %>% simulate %>% take-a-look


model:

intervention:

Your turn

Your turn

Let’s make this model

Code up the model

Block $PARAM [R] declare and initialize model parameters

$PARAM CL = 1, VC = 20, KA=1.2

Block $CMT [txt] declare model compartments

Example $CMT

$CMT GUT CENT

Example $INIT

$INIT GUT = 0, CENT = 10

Block $ODE [C++] write differential equations

$ODE
dxdt_GUT  = -KA*GUT;
dxdt_CENT =  KA*GUT - (CL/VC)*CENT;

Derive new variables in your model

To get a numeric variable, use double

double x = 5.4;

Other types you might use

bool cured = false;
int i = 2;

If in doubt, use double; it’s what you want most of the time.

Block $MAIN [C++] covariate model & initials

For example

$MAIN
double CL = TVCL*pow(WT/70,0.75);
double VC = TVVC*pow(0.86,SEX);
RESP_0 = KIN/KOUT;

In this example

C++ expressions and functions

if(a == 2) b = 2;
if(b <= 2) {
  c=3;
} else {
  c=4;
}
double d = pow(base,exponent);
double e = exp(3);
double f = fabs(-4);
double g = sqrt(5);
double h = log(6);
double i = log10(7);
double j = floor(4.2);
double k = ceil(4.2);

Lots of help on the web http://en.cppreference.com/w/cpp/numeric/math/tgamma

Preprocessor directives (#define)

$GLOBAL
#define CP (CENT/VC)
#define g 9.8
$ODE
double INH = CP/(EC50+CP);
dxdt_RESP = KIN*(1-INH) - KOUT*RESP;
$CAPTURE CP

Introduction to POPULATION simulation

Block $OMEGA and $SIGMA [txt]

$OMEGA 0.1 0.2 0.3

More $OMEGA and $SIGMA

$OMEGA @block
0.1 0.0947 0.2
$OMEGA @cor
0.1 0.67 0.2

Block $OMEGA and $SIGMA

$OMEGA @name PK
0 0 0
$OMEGA @name PD
0 0

Users are encouraged to add labels

$OMEGA @name PK @labels ECL EVC EKA
0 0 0

About @ macros

Two forms:

Closed-form PK models with $PKMODEL

$PKMODEL cmt = "GUT CENT PERIPH", depot=TRUE
$PARAM CL = 1 , V2 = 30, Q = 8, V3 = 400, KA=1.2

More $PKMODEL

$PKMODEL ncmt=1, depot=TRUE
$CMT GUT CENT
$PARAM TVCL = 1 , TVV = 30, KA=1.2
$OMEGA @labels ECL EVC
1 1
$MAIN
double CL = TVCL*exp(ECL);
double V  = TVV *exp(EVC);

Bioavailability, dosing lag time, and infusions

Read in a model object with caching

First time to read

mod <- mread_cache("simple", "../../model")

Next time to read

mod <- mread_cache("simple", "../../model")

Model build directory (soloc)

It can be helpful to set

options(mrgsolve.soloc = "my_build_directory")
mod <- mread("simple", "model", soloc = "my_build_directory")

Different ways to call mread

mod <- mread("csa.txt", "../../model")

and

mod <- mread("../../model/csa.txt")

Inline model specification

We haven’t covered the specifics of coding a model yet

code <- '
$PARAM CL = 1, V = 20
$PKMODEL cmt = "CENT"
'
mod <- mcode("dont_do_this", code)
mod <- mcode_cache("seriously_dont", code)

Controlling output - request

mod %>% mrgsim() %>% head(n = 2)
.   ID time GUT CENT CP
. 1  1    0   0    0  0
. 2  1    1   0    0  0
mod %>% Req(CP) %>% mrgsim() %>% head(n = 2)
.   ID time CP
. 1  1    0  0
. 2  1    1  0

Controlling output - obsonly

mod %>% ev(amt=1) %>% mrgsim() %>% head(n = 2)
.   ID time GUT CENT CP
. 1  1    0   0    0  0
. 2  1    0   1    0  0
mod %>% ev(amt=1) %>% obsonly() %>% mrgsim() %>% head(n = 2)
.   ID time       GUT      CENT         CP
. 1  1    0 0.0000000 0.0000000 0.00000000
. 2  1    1 0.3011942 0.6782976 0.03391488